首页 / 技术类 / C++ / 利用已有的 bind 构造 ScopeExit

利用已有的 bind 构造 ScopeExit

2012-05-20 23:07:00

对于 ScopeExit,以前有提到过(见《这种代码结构如何组织?goto or do…while(0)?》)。使用场景再简单提一下:

 1bool GenFile()
 2{
 3    HANDLE hFile = CreateFile(_T("Test.txt"), GENERIC_WRITE, 0, NUL, CREATE_ALWAYS, 0, NULL);
 4
 5    if (hFile == INVALID_HANDLE_VALUE)
 6    {
 7        return false;
 8    }
 9
10    CString strData = _T("test");
11    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
12    DWORD dwWritten = 0;
13
14    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
15    {
16        CloseHandle(hFile);
17        return false;
18    }
19
20//     if (...)
21//     {
22//         CloseHandle(hFile);
23//         return false;
24//     }
25//
26//     ...
27//
28
29    CloseHandle(hFile);
30
31    return true;
32}

如上面这部分代码,如果 if … 之类的流程持续下去(如注释部分),每个 return false 之前都得带上 CloseHandle(),非常累赘。因此,出现了类似的 ScopeExit。boost 里有一个 BOOST_SCOPE_EXIT,Loki 里面也有一个 ScopeGuard。

继续使用刚才的案例,BOOST_SCOPE_EXIT 用法:

 1bool GenFile()
 2{
 3    HANDLE hFile = CreateFile(_T("Test.txt"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 4
 5    if (hFile == INVALID_HANDLE_VALUE)
 6    {
 7        return false;
 8    }
 9
10    BOOST_SCOPE_EXIT((hFile))
11    {
12        CloseHandle(hFile);
13    }
14    BOOST_SCOPE_EXIT_END
15
16    CString strData = _T("test");
17    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
18    DWORD dwWritten = 0;
19
20    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
21    {
22        return false;
23    }
24
25//     if (...)
26//     {
27//         return false;
28//     }
29//
30//     ...
31//
32
33    return true;
34}

这样,每个 return 之前再也不必背负 CloseHandle 的包袱。

Loki::ScopeGuard 的用法:

 1bool GenFile()
 2{
 3    HANDLE hFile = CreateFile(_T("Test.txt"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 4
 5    if (hFile == INVALID_HANDLE_VALUE)
 6    {
 7        return false;
 8    }
 9
10    LOKI_ON_BLOCK_EXIT(CloseHandle, hFile);
11
12    CString strData = _T("test");
13    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
14    DWORD dwWritten = 0;
15
16    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
17    {
18        return false;
19    }
20
21//     if (...)
22//     {
23//         return false;
24//     }
25//
26//     ...
27//
28
29    return true;
30}

从使用的简洁程度上看,Loki 更胜一筹。

另外,我们经常也遇到有条件的执行清理动作的情形,boost 和 Loki 都支持。先看 Loki 的使用案例:

 1bool GenFile()
 2{
 3    LPCTSTR FILE_NAME = _T("Test.txt");
 4    HANDLE hFile = CreateFile(FILE_NAME, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 5
 6    if (hFile == INVALID_HANDLE_VALUE)
 7    {
 8        return false;
 9    }
10
11    Loki::ScopeGuard sgDeleteFile = Loki::MakeGuard(DeleteFile, FILE_NAME);
12    LOKI_ON_BLOCK_EXIT(CloseHandle, hFile);
13
14    CString strData = _T("test");
15    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
16    DWORD dwWritten = 0;
17
18    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
19    {
20        return false;
21    }
22
23//     if (...)
24//     {
25//         return false;
26//     }
27//
28//     ...
29//
30
31    sgDeleteFile.Dismiss();
32
33    return true;
34}

一开始,我们使用具名的 ScopeGuard,绑定了一个 DeleteFile(FILE_NAME) 的操作,到最后通过 Dismiss,让此操作不被执行。

相应地,boost 中,可以在进入 scope exit 之前设定一个变量,将此变量捕获入 scope exit,到最后给这个变量赋值,决定执不执行:

 1bool GenFile()
 2{
 3    LPCTSTR FILE_NAME = _T("Test.txt");
 4    HANDLE hFile = CreateFile(FILE_NAME, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 5
 6    if (hFile == INVALID_HANDLE_VALUE)
 7    {
 8        return false;
 9    }
10
11    bool bOK = false;
12
13    BOOST_SCOPE_EXIT((hFile)(&bOK))
14    {
15        if (!bOK)
16        {
17            CloseHandle(hFile);
18        }
19    }
20    BOOST_SCOPE_EXIT_END
21
22    CString strData = _T("test");
23    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
24    DWORD dwWritten = 0;
25
26    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
27    {
28        return false;
29    }
30
31//     if (...)
32//     {
33//         return false;
34//     }
35//
36//     ...
37//
38
39    bOK = true;
40
41    return true;
42}

注意,此处捕获 bOK 的时候才用指针的形式,以保证最后对此变量的修改能影响到 scope exit 内部。

好,预备知识简要介绍到这里。下面我们进入正题,实现一个类似的东西。

为啥要放在现在说这个事情呢?因为前几天刚弄了个 Bind,使用 Bind 实现 Scope Exit 比较简单。从 boost 和 Loki 的两个方案来看,个人更喜欢 Loki 的,boost 玩语法玩得太厉害。通常我们这种需求都是一两行代码(比如上面例子中的 CloseHandle,DeleteFile之类的),而 boost 方案是用来填写一大段代码的,一两行代码的情形下用起来不方便,重复的框架性代码就占了四行,有效功能才占一行,性价比太低了。

Loki 的实现中,有对于多个参数的处理,搞出了 ScopeGuardImpl0、ScopeGuardImpl1、ScopeGuardImpl2、…… 这在 Loki 中没有类似 boost::bind 的设施的情况下是必须的。这里插播一句,Loki 的 Bind 相当的不好用,只能绑定第一个,然后再绑定第一个,…,从而才能完成对所有参数的绑定。boost::bind 灵活多了。有了类似 boost::bind 的设施后,我们利用它山寨一个 Loki::ScopeGuard。当然,我现在是在写 xl::ScopeExit,当然会用 xl::Bind 去实现。本文暂不限定是 xl::Bind 还是 boost::bind。设个预编译开关吧:

 1#define USING_BOOST_BIND
 2
 3#ifdef USING_BOOST_BIND
 4#define BOOST_BIND_ENABLE_STDCALL
 5#include <boost/bind.hpp>
 6#define SCOPE_EXIT_BIND ::boost::bind
 7#else
 8#include <xl/Meta/xlBind.h>
 9#define SCOPE_EXIT_BIND ::xl::Bind
10#endif
11高亮的那句是为了打开 boost::bind  __stdcall 调用约定的支持。然后抄 Loki  ScopeGuardImplBase
12class ScopeGuardImplBase
13{
14public:
15    ScopeGuardImplBase() : m_bDismissed(false)
16    {
17
18    }
19
20    ScopeGuardImplBase(ScopeGuardImplBase &that) :
21        m_bDismissed(that.m_bDismissed)
22    {
23        that.Dismiss();
24    }
25
26    ~ScopeGuardImplBase()
27    {
28
29    }
30
31protected:
32    template <typename J>
33    static void StaticExecute(J &j)
34    {
35        if (!j.m_bDismissed)
36        {
37            j.Execute();
38        }
39    }
40
41public:
42    void Dismiss() const
43    {
44        m_bDismissed = true;
45    }
46
47private:
48    mutable bool m_bDismissed;
49};
50
51typedef const ScopeGuardImplBase& ScopeGuard;

Loki 在 j.Execute 中有 try … catch …,个人认为不该有(见 http://www.cppblog.com/Streamlet/archive/2011/03/23/142543.html),所以去掉。

然后对于 ScopeGuardImpl,我们抛开 0、1、2,使用一个超级简洁的实现:

 1template <typename F>
 2class ScopeGuardImpl : public ScopeGuardImplBase
 3{
 4public:
 5    ScopeGuardImpl(F fn) :
 6        ScopeGuardImplBase(), m_fn(fn)
 7    {
 8
 9    }
10
11    ~ScopeGuardImpl()
12    {
13        StaticExecute(*this);
14    }
15
16    void Execute()
17    {
18        m_fn();
19    }
20
21private:
22    F m_fn;
23};

我们只需要一个参数,一个兼容函数签名“void ()”的可执行对象 F fn。我们保存这个 fn 直到析构的时候去执行它。 已经差不多了,为了使用简便,再抄一个 MakeGuard:

1template <typename F>
2inline ScopeGuardImpl<F> MakeGuard(F f)
3{
4    return ScopeGuardImpl<F>(f);
5}

最后,定义一个可供匿名使用的宏 XL_ON_BLOCK_EXIT:

1#define XL_CONN_(s, t)  s##t
2#define XL_CONN(s, t)   XL_CONN_(s, t)
3#define XL_ON_BLOCK_EXIT(...) ScopeGuard XL_CONN(sg, __LINE__) = MakeGuard(SCOPE_EXIT_BIND(__VA_ARGS__))

同 Loki 一样,我们使用行号作为“匿名”变量的命名。注意 MakeGuard 后的参数里,需要填写如整个 bind,这在具名使用的时候书写上会麻烦一点点。

好了,已实现完毕,我们再一次用前面的例子来使用它:

 1bool GenFile()
 2{
 3    LPCTSTR FILE_NAME = _T("Test.txt");
 4    HANDLE hFile = CreateFile(FILE_NAME, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 5
 6    if (hFile == INVALID_HANDLE_VALUE)
 7    {
 8        return false;
 9    }
10
11    ScopeGuard sgDeleteFile = MakeGuard(SCOPE_EXIT_BIND(DeleteFile, FILE_NAME));
12    XL_ON_BLOCK_EXIT(CloseHandle, hFile);
13
14    CString strData = _T("test");
15    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
16    DWORD dwWritten = 0;
17
18    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
19    {
20        return false;
21    }
22
23//     if (...)
24//     {
25//         return false;
26//     }
27//
28//     ...
29//
30
31    sgDeleteFile.Dismiss();
32
33    return true;
34}

高亮部分,就是比 Loki 使用起来麻烦的地方。如果明确了是使用 boost::bind 或 xl::Bind,那就写成:

1ScopeGuard sgDeleteFile = MakeGuard(boost::bind(DeleteFile, FILE_NAME));

1ScopeGuard sgDeleteFile = MakeGuard(xl::Bind(DeleteFile, FILE_NAME));

(有个区别是,如果绑定成员函数,boost::bind 的对象指针在第二个参数,xl::Bind 在第一个参数。)

好了,写到这里。请各位指教。

至此,xlLib 里面玩语法的部分也就差不多了。接下来将会注重功能性的东西。


首发:http://www.cppblog.com/Streamlet/archive/2012/05/20/175510.html



NoteIsSite/0.4